/**
 *  @file Socket.cpp
 */
#include "Socket.h"
#include "SocketException.h"
#include "../util/AtomicOperations.h"
#include "../common/CppflibMain.h"
#include "../common/ObjPtrT.h"
#include "../common/UtilFuncT.h"
#include "../common/__internal/InternalLock.h"
#include "../lang/Primitive.h"
#include "__internal/SocketToggleBlockingState.h"

#if defined(WIN32)
#   include <winsock2.h>
#   include <Ws2tcpip.h>
#   pragma comment(lib, "ws2_32.lib")
#   include <stdio.h>
#elif defined(linux)
#   include <string.h>
#   include <sys/types.h>
#   include <sys/socket.h>
#   include <sys/select.h>
#   include <sys/ioctl.h>
#   include <netdb.h>
#   include <netinet/in.h>
#   include <arpa/inet.h>
#   include <unistd.h>
#   include <fcntl.h>
#   include <errno.h>
#   include <stdio.h>
#endif

using namespace cppflib::lang;

namespace cppflib
{

namespace net
{

#if defined(linux)
#define INVALID_SOCKET ((ptr_t)(~0))
#define SOCKET_ERROR           (-1)

typedef struct sockaddr SOCKADDR;
typedef struct sockaddr_in SOCKADDR_IN; 
#endif


#if defined(WIN32)

PRIVATE volatile u32_t st_wsaInitializationCount = 0;

/**
 *  Initializing socket environment is required only on Win32 platform
 */
PRIVATE bool __private_InitSocketEnv(void * pWsaData /* = NULL */)
{
   if (util::AtomicIncrementFetch(&st_wsaInitializationCount) > 1)
      return true;

   // initialize Winsock library
   const int WS_MAJOR = 2;
   const int WS_MINOR = 2;

   WSADATA wsaData;
   WSADATA *pTempWsaData = (pWsaData != NULL) ? reinterpret_cast<WSADATA*>(pWsaData) : &wsaData;
   WORD wVersionRequested = MAKEWORD(WS_MAJOR, WS_MINOR);
   int nResult = ::WSAStartup(wVersionRequested, pTempWsaData);

   if (nResult != 0)
      return false;
/*
   if (LOBYTE(pTempWsaData->wVersion) != WS_MAJOR || HIBYTE(pTempWsaData->wVersion) != WS_MINOR) {
      ::WSACleanup();
      return false;
   }
*/

   return true;
}

/**
 *  Clean up before exiting program
 */
PRIVATE bool __private_CleanupSocketEnv(void)
{
   if (util::AtomicDecrementFetch(&st_wsaInitializationCount) > 0)
      return true;

   ::WSACleanup();
   
   return true;
}

PRIVATE bool __InitSocketEnv(void * pWsaData /* = NULL */)
{
   bool ret = false;
   INTL_AcquireLock(LP_SOCKETRESOURCE);
   ret = __private_InitSocketEnv(pWsaData);
   INTL_ReleaseLock(LP_SOCKETRESOURCE);
   return ret;
}

PRIVATE bool __CleanupSocketEnv(void)
{
   bool ret = false;
   INTL_AcquireLock(LP_SOCKETRESOURCE);
   ret = __private_CleanupSocketEnv();
   INTL_ReleaseLock(LP_SOCKETRESOURCE);
   return ret;
}

#endif


const pcwstr_t Socket::LOCALHOST = _S("127.0.0.1");

/**
 *  Class contains info to cancel a Socket operation with timeout immediately.
 *
 *  The idea is to let the "select" call to wait on 2 sockets (one is the Socket itself
 *  and the other an extra UDP socket) when calling ChkPendingData or ChkReadyToWrite.
 *  To cancel a Socket operation, just send a few bytes to the extra UDP socket (see CancelWaiting) and "select"
 *  call will return immediately. Then check whether only the extra UDP socket has pending data
 *  (see ChkPendingData and ChkReadyToWrite). If so, it is known that user has cancelled the call explicitly.
 */
class SocketCancelContext : public ICancelContext
{

private:
   Socket *pWaitSock;   ///< the extra UDP socket
   volatile u32_t waitingCount;  ///< 1: waiting, 0: not waiting
   bool enableCancel;

   void ReleaseBeforeThrowing()
   {
      cppmempool::Release(pWaitSock);
   }

public:
   SocketCancelContext()
   {
      pWaitSock = new Socket();
      cppmempool::Retain(pWaitSock);
      if (!pWaitSock->Create(CFString(Socket::LOCALHOST), 0, Socket::Dgram))
         THROW_CTOR_EXCEP(SocketException, _S("Create waiting socket failed"));

      waitingCount = 0;
      enableCancel = true;
   }

   virtual ~SocketCancelContext()
   {
      ReleaseBeforeThrowing();
   }

   /**
    *  Remove all pending data in the socket buffer
    */
   void DrainPendingData()
   {
      u8_t tempBuf[8];
      while (pWaitSock->ChkPendingData(0) > 0)
         pWaitSock->Receive(tempBuf, sizeof(tempBuf), 0);
   }

   void SetWaiting(bool isWaiting)
   {
      if (isWaiting)
         util::AtomicIncrementFetch(&waitingCount);
      else 
         util::AtomicDecrementFetch(&waitingCount);
   }

   bool IsWaiting()
   {
      return (util::AtomicRead(&waitingCount) > 0);
   }

   ptr_t GetInternalSock()
   {
      return pWaitSock->sock;
   }

   void SetEnableCancel(bool b)
   {
      enableCancel = b;
   }

   /**
    *  Cancel waiting by sending some bytes to the waiting socket such that
    *  the "select" call can return immediately
    */
   virtual bool CancelWaiting()
   {
      if (!enableCancel)
         return false;

      if (!IsWaiting())
         return false;

      CFString addr;
      u16_t port = 0;
      pWaitSock->GetLocalAddress(addr, port);  // get port of waiting socket

      CFString localHostAddr(Socket::LOCALHOST);
      Socket tempSocket;
      if (!tempSocket.Create(localHostAddr, 0, Socket::Dgram)) // make a temp socket
         return false;

      u8_t tempData[4] = {0};
      tempSocket.SendTo(tempData, sizeof(tempData), 0, localHostAddr, port); // send data to waiting socket
      tempSocket.Close();
      return true;
   }
};

/**
 *  Class to toggle the waiting state of CancelContext
 */
class CancelContextToggleWaitingState : public NoCopyAndNew
{
private:
   SocketCancelContext *pCancelContext;

private:
   CancelContextToggleWaitingState() { }

public:
   explicit CancelContextToggleWaitingState(SocketCancelContext *pCancelContext)
   {
      this->pCancelContext = pCancelContext;
      this->pCancelContext->SetWaiting(true);
      this->pCancelContext->DrainPendingData();  // drain data, too
   }

   ~CancelContextToggleWaitingState()
   {
      this->pCancelContext->SetWaiting(false);
      this->pCancelContext->DrainPendingData();  // drain data, too
   }
};


/***************************** Socket class ************************************/

Socket::Socket(void)
{
#if defined(WIN32)
   __InitSocketEnv(NULL);
#endif

   this->sockAttr = new util::BitAttributeT<sockattr_t>();
   cppmempool::Retain(this->sockAttr);

   this->pCancelContext = NULL;

   Initialize(); 
}

Socket::~Socket(void)
{
   Close();

   if (pCancelContext)
      cppmempool::Release(pCancelContext);

   cppmempool::Release(sockAttr);

#if defined(WIN32)
   __CleanupSocketEnv();
#endif
}

void Socket::Initialize(void)
{
   this->sock = INVALID_SOCKET;
   this->sockAttr->SetAttr(SA_ISBLOCKING, true); // socket by default is blocking
   this->sockAttr->SetAttr(SA_ISCONNECTED, false);
}

/**
 *  Set socket handle. Used in "Accept".
 *
 *  @param[in] sock -- handle of a new socket
 */
void Socket::SetHandle(ptr_t sock)
{
   if (this->sock == INVALID_SOCKET)
      this->sock = sock;
}

/**
 *  For derived class to get internal socket handle
 */
ptr_t Socket::GetInternalSock()
{
   return this->sock;
}


/**
 *  Create socket and bind socket to an address/port
 *
 *  @param[in] socketAddress -- address of this socket. may be in
 *                              "xxx.xxx.xxx.xxx" or "my.abc.com".
 *                              (Pass empty string to open socket on all interfaces.)
 *  @param[in] socketPort -- port number (Pass 0 to let system pick one for you)
 *  @param[in] socketType -- type of socket 
 *
 *  @return true -- init OK
 *  @return false -- init fail
 */
bool Socket::Create(const CFString &socketAddress, u16_t socketPort, SocketType socketType)
{
    if (this->sock != INVALID_SOCKET)
		return false;

	// create socket
	this->sock = ::socket(PF_INET, static_cast<int>(socketType), 0);
	if (this->sock == INVALID_SOCKET)
		return false;

	// then bind it	
	return Bind(socketPort, socketAddress);
}

/**
 *  Bind socket to an address/port
 *
 *  @param[in] socketPort -- port number 
 *  @param[in] socketAddress -- address of this socket. may be in
 *                              "xxx.xxx.xxx.xxx" or "my.abc.com".
 *                              Pass empty string to open socket on all interfaces.
 *
 *  @return true -- bind OK
 *  @return false -- bind fail
 */
bool Socket::Bind(u16_t socketPort, const CFString &socketAddress)
{
    SOCKADDR_IN sockAddr;
    ::memset(&sockAddr, 0, sizeof(sockAddr));

	sockAddr.sin_family = AF_INET;

	if (socketAddress.IsEmpty()) {
		sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	}
	else {
	   
	   ObjPtrT<char> szTemp(socketAddress.ToUtf8String());
	   u32_t lResult = ::inet_addr(szTemp);
	   if (lResult == INADDR_NONE) 	{
			return false;
	   }
	   sockAddr.sin_addr.s_addr = lResult;
	}

	sockAddr.sin_port = htons(socketPort);

	return (SOCKET_ERROR != ::bind(this->sock, (SOCKADDR*)&sockAddr, sizeof(sockAddr)));
}

/**
 *  Test if socket has been initialized
 */
bool Socket::IsValidSock()
{
    return (this->sock != INVALID_SOCKET);
}

/**
 *  Get socket type
 */
Socket::SocketType Socket::GetSocketType()
{
    int soType = Socket::UnknownType;
#if defined(WIN32) 
    int optLen = sizeof(int);
    if (::getsockopt(this->sock, SOL_SOCKET, SO_TYPE, reinterpret_cast<char*>(&soType), &optLen) != SOCKET_ERROR) {
#elif defined(linux)
    socklen_t optLen = sizeof(int);
    if (::getsockopt(this->sock, SOL_SOCKET, SO_TYPE, &soType, &optLen) != SOCKET_ERROR) {
#endif
       if (soType >= Socket::Stream && soType <= Socket::Raw)
          return static_cast<Socket::SocketType>(soType);
    }
    
    return Socket::UnknownType;  
}

/**
 *  Get address info from SOCKADDR_IN
 */
PRIVATE void __GetAddrInfo(SOCKADDR_IN * pSockAddr, CFString &address, u16_t &port)
{
   char * szAddr = ::inet_ntoa(pSockAddr->sin_addr);
   address = (const char*)szAddr;
   port = ntohs(pSockAddr->sin_port);   
}

/**
 *  Get peer's address for a connected socket
 *
 *  @param[out] peerAddress -- to hold the returned address
 *  @param[out] peerPort -- to hold the returned port number
 *
 *  @return true -- return OK
 *  @return false -- return fail
 */
bool Socket::GetPeerAddress(CFString &peerAddress, u16_t &peerPort)
{
    SOCKADDR_IN sockAddr;
	::memset(&sockAddr, 0, sizeof(sockAddr));

	int nSockAddrLen = sizeof(sockAddr);

#if defined(WIN32)
    bool bResult = (::getpeername(this->sock, (SOCKADDR*)&sockAddr, &nSockAddrLen) != SOCKET_ERROR);
#elif defined(linux)
	bool bResult = (::getpeername(this->sock, (SOCKADDR*)&sockAddr, reinterpret_cast<socklen_t*>(&nSockAddrLen)) != SOCKET_ERROR);
#endif

	if (bResult) {
        __GetAddrInfo(&sockAddr, peerAddress, peerPort);
	}
	
	return bResult;
}

/**
 *  Get local address
 *
 *  @param[out] localAddress -- to hold the returned address
 *  @param[out] localPort -- to hold the returned port number
 *
 *  @return true -- return OK
 *  @return false -- return fail
 */
bool Socket::GetLocalAddress(CFString &localAddress, u16_t &localPort)
{
    SOCKADDR_IN sockAddr;
	::memset(&sockAddr, 0, sizeof(sockAddr));

	int nSockAddrLen = sizeof(sockAddr);

#if defined(WIN32)
    bool bResult = (::getsockname(this->sock, (SOCKADDR*)&sockAddr, &nSockAddrLen) != SOCKET_ERROR);
#elif defined(linux)
	bool bResult = (::getsockname(this->sock, (SOCKADDR*)&sockAddr, reinterpret_cast<socklen_t*>(&nSockAddrLen)) != SOCKET_ERROR);
#endif

	if (bResult) {
        __GetAddrInfo(&sockAddr, localAddress, localPort);
	}
	
	return bResult;
}

/**
 *  A listening socket to accept connection
 *
 *  @param[out] connectedSocket -- user provide an invalid socket
 *
 *  @return true -- accept OK
 *  @return false -- accept fail
 */
bool Socket::Accept(Socket &connectedSocket)
{
    if (connectedSocket.IsValidSock())
       return false;

    ptr_t hSock = ::accept(this->sock, NULL, NULL);

	if (hSock != INVALID_SOCKET)
		connectedSocket.SetHandle(hSock);

	return (hSock != INVALID_SOCKET);
}

/**
 *  A listening socket to accept connection
 *
 *  @return a new accepted socket
 *  @return NULL if failed
 */
Socket * Socket::Accept()
{
   Socket *pNewSock = new Socket();
   if (!Socket::Accept(*pNewSock)) {
      cppmempool::Release(pNewSock);
      pNewSock = NULL;
   }

   return pNewSock;
}

/**
 *  Close socket
 */
void Socket::Close()
{
	if (IsValidSock()) {
#if defined(WIN32)
		::closesocket(this->sock);
#elif defined(linux)
		::close(static_cast<int>(this->sock));
#endif
		Initialize();
	}
}

/**
 *  A replacement for gethostbyname
 */
PRIVATE u32_t __GetNumericAddr(const CFString &hostAddress)
{
    struct addrinfo hints;
    struct addrinfo *result = NULL;

    // fill in hints structure
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;    // Allow IPv4 or IPv6
    hints.ai_socktype = SOCK_DGRAM; // Datagram socket
    hints.ai_flags = AI_PASSIVE;    // For wildcard IP address
    hints.ai_protocol = IPPROTO_UDP;
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;

    u32_t numericAddr = INADDR_NONE;
    ObjPtrT<char> szTemp(hostAddress.ToUtf8String());
    if (getaddrinfo(szTemp, NULL, &hints, &result) == 0) {

       if (result) { // take the first entry only
          numericAddr = reinterpret_cast<SOCKADDR_IN*>(result->ai_addr)->sin_addr.s_addr;
       }
    }

    if (result) freeaddrinfo(result);

    return numericAddr;
}

/**
 *  Connect to a remote server.
 *
 *  Both a connection-oriented(stream) or a connection-less(datagram) socket can call this
 *  Connect function.
 *
 *  @param[in] hostAddress -- remote host address ("xxx.xxx.xxx.xxx" or "remote.abc.com")
 *  @param[in] hostPort -- remote port number
 *
 *  @return true -- connect OK
 *  @return false -- connect fail
 */
bool Socket::Connect(const CFString &hostAddress, u16_t hostPort)
{
    bool isStreamSock = (GetSocketType() == Socket::Stream);
    if (isStreamSock && sockAttr->IsAttrSet(SA_ISCONNECTED))
       return true;

    bool isOK = false;
    SOCKADDR_IN sockAddr;
	::memset(&sockAddr, 0, sizeof(sockAddr));
	sockAddr.sin_family = AF_INET;

    u32_t lResult = __GetNumericAddr(hostAddress);

    if (lResult != INADDR_NONE) {
   	       
	   sockAddr.sin_addr.s_addr = lResult;
	   sockAddr.sin_port = htons(hostPort);
    
       int err = ::connect(this->sock, (SOCKADDR*)&sockAddr, sizeof(sockAddr));
	   if (err != SOCKET_ERROR) {
          isOK = true;
       }
       else {
#if defined(WIN32)
          if (::WSAGetLastError() == WSAEISCONN) // already connected
             isOK = true;
#elif defined(linux)
          if (cppflib::GetLastError() == EISCONN)   // already connected
             isOK = true;
#endif 
       }

    } 

    if (isStreamSock && isOK)
       sockAttr->SetAttr(SA_ISCONNECTED, true);

    return isOK;
}

/**
 *  Connect with a timeout (Only work for TCP socket)
 */
bool Socket::Connect(const CFString &hostAddress, u16_t hostPort, int timeout)
{
    if (GetSocketType() == Socket::Stream) {  // only work for TCP socket
      
       if (sockAttr->IsAttrSet(SA_ISCONNECTED))
          return true;

       bool isOK = false;
       bool isChkWritability = false;
       SocketToggleBlockingState toggleBlockingState(this, false);

       if (Socket::Connect(hostAddress, hostPort)) {
          isOK = true;
       }
       else {

#if defined(WIN32)
          if (::WSAGetLastError() == WSAEWOULDBLOCK)
             isChkWritability = true;
#elif defined(linux)
          if (cppflib::GetLastError() == EINPROGRESS)
             isChkWritability = true;
#endif
       }

       if (isChkWritability && ChkReadyToWrite(timeout) > 0) {

#if defined(WIN32)
          isOK = true;
#elif defined(linux)
          // Linux require to further read 'SO_ERROR' to confirm whether 'connect' succeeds or not
          int soError = -1;
          socklen_t optLen = sizeof(int);
          if (::getsockopt(this->sock, SOL_SOCKET, SO_ERROR, &soError, &optLen) != SOCKET_ERROR) {
             if (soError == 0)
                isOK = true;
          }
#endif
       }

       if (isOK)
          sockAttr->SetAttr(SA_ISCONNECTED, true);

       return isOK;
    }
    
    // for other socket type, just ignore the timeout value
    return Socket::Connect(hostAddress, hostPort);
    
}

/**
 *  To enable/disable the blocking mode of socket.
 *
 *  Socket is blocking by default. Call this to change the blocking nature.
 *
 *  @note If connection-oriented socket is set to non-blocking, "Connect"
 *        will return immediately and report fail all the time.
 *
 *  @param[in] isEnableBlock -- true : set to blocking mode.
 *                              false : set to non-blocking mode
 *
 *  @return true -- set OK
 *  @return false -- set fail
 */
bool Socket::SetBlocking(bool isEnableBlock)
{
    if (sockAttr->IsAttrSet(SA_ISBLOCKING) == isEnableBlock) // do nothing if already the same
       return true;

    bool isOK = false;

#if defined(WIN32)
	u32_t lpArgument = isEnableBlock ? 0 : 1;
	isOK = (SOCKET_ERROR != ::ioctlsocket(this->sock, FIONBIO, reinterpret_cast<u_long*>(&lpArgument)));
#elif defined(linux)
    int orgFlags = ::fcntl(this->sock, F_GETFL);
    orgFlags = isEnableBlock ? (orgFlags & ~O_NONBLOCK) : (orgFlags | O_NONBLOCK);
    isOK = (0 == ::fcntl(this->sock, F_SETFL, orgFlags));
#endif

    if (isOK)
       sockAttr->SetAttr(SA_ISBLOCKING, isEnableBlock);
    
    return isOK;
}

/**
 *  Whether socket is blocking or not
 */
bool Socket::IsBlocking()
{
    return sockAttr->IsAttrSet(SA_ISBLOCKING);
}

/**
 *  To enable/disable the broadcast feature of a socket
 *
 *  @param [in] isEnableBroadcast -- true: enable
 *                                   false : disable
 *
 *  @return TRUE -- OK
 *  @return FALSE -- failed
 */
bool Socket::SetBroadcast(bool isEnableBroadcast)
{
   u8_t lpArgument = isEnableBroadcast ? 1 : 0;
   return (0 == ::setsockopt(this->sock, SOL_SOCKET, SO_BROADCAST, 
                             (const char*)&lpArgument, sizeof(u8_t)));
}

/**
 *  Test whether this socket is already closed by peer
 *
 *  @return true: already closed by peer
 */
bool Socket::IsClosedByPeer(void)
{
   int ret = ChkPendingData(1);
   if (ret < 0)
      return true;  // error, regard as closed

   if (ret > 0) {
      u32_t pendingBytes = 0;
#if defined(WIN32)
      if (::ioctlsocket(this->sock, FIONREAD, reinterpret_cast<u_long*>(&pendingBytes)) != SOCKET_ERROR) {
         if (pendingBytes == 0)
            return true;
      }
#elif defined(linux)
      if (::ioctl(this->sock, FIONREAD, &pendingBytes) == 0) {
         if (pendingBytes == 0)
            return true;
      }
#endif
   }

   return false;
   
}

/**
 *  To get the context for cancelling a waiting operation
 *
 *  @note SA_CANCELWAIT flag must be enabled before getting this context
 *
 *  @note Caller must call cppmempool::Retain on this context after obtaining this
 *        pointer and call cppmempool::Release when no long use.
 */
ICancelContext * Socket::GetCancelContext()
{
   return pCancelContext;
}

/**
 *  A connection-oriented socket get into listening mode
 *
 *  @param[in] connectionBacklog -- number of connection to accept at the same time
 *
 *  @return true -- OK
 *  @return false -- fail
 */
bool Socket::Listen(int connectionBacklog)
{
   return (SOCKET_ERROR != ::listen(this->sock, connectionBacklog));
}

/**
 *  Receive bytes from peer.
 *
 *  Both connected or connection-less socket can call this function.
 *
 *  @param[out] pBuf -- buffer to hold received bytes
 *  @param[in] bufLen -- length of buffer in byte
 *  @param[in] nFlags -- flags to set
 *
 *  @return > 0 -- number of bytes received
 *  @return 0 -- socket is closed by peer
 *  @return < 0 -- error
 */
int Socket::Receive(u8_t * pBuf, int bufLen, int nFlags)
{
   return ::recv(this->sock, reinterpret_cast<char*>(pBuf), bufLen, nFlags);
}

/**
 *  Receive with a timeout value.
 *  Both connected or connection-less socket can call this function.
 *
 *  @param[in] timeout -- timeout in milliseconds
 *  @param[out] pBuf -- buffer to hold received bytes
 *  @param[in] bufLen -- length of buffer in byte
 *  @param[in] nFlags -- flags to set
 *
 *  @return > 0 -- number of bytes received
 *  @return 0 -- error or socket is closed by peer
 *  @return -1 -- timeout
 */
int Socket::ReceiveTimeout(int timeout, u8_t * pBuf, int bufLen, int nFlags)
{
   int recvByte = 0;
   
   int ret = ChkPendingData(timeout);
   if (ret > 0) {
      recvByte = Socket::Receive(pBuf, bufLen, nFlags);
   }
   else if (ret == 0) {
      recvByte = -1;    // timeout
   }
   
   return recvByte;
}

/**
 *  Receive bytes from peer with peer address returned.
 *
 *  Only connection-less socket can call this function.
 *
 *  @param[out] pBuf -- buffer to hold received bytes
 *  @param[in] bufLen -- length of buffer in byte
 *  @param[in] nFlags -- flags to set
 *  @param [in, out] -- peerAddress
 *  @param [in, out] -- peerPort
 *
 *  @return number of bytes received
 */
int Socket::ReceiveFrom(u8_t * pBuf, int bufLen, int nFlags, CFString &peerAddress, u16_t &peerPort)
{
   SOCKADDR_IN sockAddr;
   int fromLen = sizeof(SOCKADDR_IN);
#if defined(WIN32)
   int recvByte = ::recvfrom(this->sock, reinterpret_cast<char*>(pBuf), bufLen, nFlags, (SOCKADDR*)&sockAddr, &fromLen);
#elif defined(linux)
   int recvByte = ::recvfrom(this->sock, reinterpret_cast<char*>(pBuf), bufLen, nFlags, 
                             (SOCKADDR*)&sockAddr, reinterpret_cast<socklen_t*>(&fromLen));
#endif

   // return peer's address
   __GetAddrInfo(&sockAddr, peerAddress, peerPort);
   
   return recvByte;
}

/**
 *  Receive with a timeout value.
 *  Only connection-less socket can call this function.
 *
 *  @param[in] timeout -- timeout in milliseconds
 *  @param[out] pBuf -- buffer to hold received bytes
 *  @param[in] bufLen -- length of buffer in byte
 *  @param[in] nFlags -- flags to set
 *  @param [out] peerAddress -- peer address string
 *  @param [out] peerPort -- peer port
 *
 *  @return number of bytes received
 */
int Socket::ReceiveFromTimeout(int timeout, u8_t * pBuf, int bufLen, int nFlags, 
                               CFString &peerAddress, u16_t &peerPort)
{
   int recvByte = 0;
      
   if (ChkPendingData(timeout) > 0) {
      recvByte = Socket::ReceiveFrom(pBuf, bufLen, nFlags, peerAddress, peerPort);    
   }
   
   return recvByte;
}

/**
 *  Send bytes to peer.
 *
 *  Both connected or connection-less socket can call this function.
 *
 *  @param[in] pBuf -- buffer to hold bytes to be sent
 *  @param[in] nBufLen -- length of buffer in byte
 *  @param[in] nFlags -- flags to set
 *
 *  @return number of bytes sent
 */
int Socket::Send(const u8_t * pBuf, int bufLen, int nFlags)
{
   return ::send(this->sock, reinterpret_cast<const char*>(pBuf), bufLen, nFlags);
}

/**
 *  Send bytes to peer.
 *
 *  Both connected or connection-less socket can call this function.
 *
 *  @param[out] pBuf -- buffer to hold bytes to be sent
 *  @param[in] bufLen -- length of buffer in byte
 *  @param[in] nFlags -- flags to set
 *  @param [in] peerAddress -- destination addres
 *  @param [in] peerPort -- destination port
 *
 *  @return number of bytes sent
 */
int Socket::SendTo(const u8_t * pBuf, int bufLen, int nFlags, const CFString &peerAddress, u16_t peerPort)
{
    SOCKADDR_IN sockAddr;
    ::memset(&sockAddr, 0, sizeof(sockAddr));
	sockAddr.sin_family = AF_INET;
	
    int nrByteSent = -1;
    ObjPtrT<char> szAddr(peerAddress.ToUtf8String());
	u32_t lResult = ::inet_addr(szAddr);
	if (lResult != INADDR_NONE) {
       
       sockAddr.sin_addr.s_addr = lResult;
	   sockAddr.sin_port = htons(peerPort);

       int toLen = sizeof(SOCKADDR_IN);
       nrByteSent = ::sendto(this->sock, reinterpret_cast<const char*>(pBuf), bufLen, nFlags, (SOCKADDR*)&sockAddr, toLen);
    }

    return nrByteSent;
}

/**
 *  Set an attribute.
 */
void Socket::SetAttribute(sockattr_t attr, const BaseObject &val)
{
   if (attr == Socket::SA_CANCELWAIT) {
      if (!cppflib::InstanceOf<Boolean>(&val)) 
         throw SocketException(_S("SetAttribute: value is not Boolean"));
      this->sockAttr->SetAttr(attr, static_cast<const Boolean*>(&val)->Value());

      if (this->sockAttr->IsAttrSet(Socket::SA_CANCELWAIT)) {
         if (!pCancelContext) { // create a context if not present yet
            pCancelContext = new SocketCancelContext();
            cppmempool::Retain(pCancelContext);
         }
      }

      pCancelContext->SetEnableCancel(static_cast<const Boolean*>(&val)->Value());
   }
}

/**
 *  Test whether a flag is set
 */
bool Socket::IsAttributeSet(sockattr_t attr)
{
   return sockAttr->IsAttrSet(attr);
}
    
/**
 *   Check whether incoming data is available
 *
 *   @param [in] timeout -- timeout in milliseconds
 *
 *   @return  > 0  -- there is incoming data
 *   @return 0 -- time is up
 *   @return  < 0  -- error
 */
int Socket::ChkPendingData(int timeout)
{
    fd_set readFd;
	struct timeval timeOutStruct;

    timeOutStruct.tv_sec = timeout / 1000;
	timeOutStruct.tv_usec = (timeout % 1000) * 1000;
	
	FD_ZERO(&readFd);	
    FD_SET(this->sock, &readFd);

    int nRet = 0;
    if (sockAttr->IsAttrSet(Socket::SA_CANCELWAIT)) {
       // if cancel waiting is enabled, wait on 2 sockets simultaneously
       CancelContextToggleWaitingState toggleWaitingState(pCancelContext);
       FD_SET(pCancelContext->GetInternalSock(), &readFd);
       ptr_t maxSocket = cppflib::Max<ptr_t>(this->sock, pCancelContext->GetInternalSock());

       nRet = ::select((int)(maxSocket + 1), &readFd, NULL, NULL, &timeOutStruct);
       if (nRet > 0) {
          if (! FD_ISSET(this->sock, &readFd) && 
                FD_ISSET(pCancelContext->GetInternalSock(), &readFd)) {
             nRet = -1;  // only "cancel" socket has data, return error
          }
       }
    }
    else {
       /*
	    * first argument is ignored in Windows while in Unix, it should be
	    * the highest descriptor plus 1
	    */
       nRet = ::select((int)(this->sock + 1), &readFd, NULL, NULL, &timeOutStruct);
    }

    return nRet;
}

/**
 *   Check whether it is ready to write
 *   (It is intended to help detecting a non-blocking "connect")
 *
 *   @param [in] timeout -- timeout in milliseconds
 *
 *   @return  > 0  -- ready to write
 *   @return 0 -- time is up
 *   @return  < 0  -- error
 */
int Socket::ChkReadyToWrite(int timeout)
{
    fd_set writeFd;
	struct timeval timeOutStruct;
	
    timeOutStruct.tv_sec = timeout / 1000;
	timeOutStruct.tv_usec = (timeout % 1000) * 1000;

	FD_ZERO(&writeFd);
    FD_SET(this->sock, &writeFd);
	
    int nRet = 0;
	if (sockAttr->IsAttrSet(Socket::SA_CANCELWAIT)) {
       // if cancel waiting is enabled, wait on 2 sockets simultaneously
       CancelContextToggleWaitingState toggleWaitingState(pCancelContext); 

       fd_set readFd;
       FD_ZERO(&readFd);          
       FD_SET(pCancelContext->GetInternalSock(), &readFd);
       ptr_t maxSocket = cppflib::Max<ptr_t>(this->sock, pCancelContext->GetInternalSock());

       nRet = ::select((int)(maxSocket + 1), &readFd, &writeFd, NULL, &timeOutStruct);
       if (nRet > 0) {
          if (! FD_ISSET(this->sock, &writeFd) && 
                FD_ISSET(pCancelContext->GetInternalSock(), &readFd)) {
                nRet = -1;  // only "cancel" socket has data, return error
          }
       }
    }
    else {
       /*
	    * first argument is ignored in Windows while in Unix, it should be
	    * the highest descriptor plus 1
	    */
       nRet = ::select((int)(this->sock + 1), NULL, &writeFd, NULL, &timeOutStruct);
    }
	
	
	return nRet;
}

/**
 *   @return  > 0  -- there is incoming data
 *   @return 0 -- time is up
 *   @return  < 0  -- error
 */
int Socket::GetPendingSockets(collections::ICollection &sockList, sz_t listSize, ptrany_t fdSet, int timeout)
{
    ptr_t maxSocket = 0;
    fd_set *pReadFd = static_cast<fd_set*>(fdSet);
	struct timeval timeOutStruct;

    FD_ZERO(pReadFd);
    for (int i = 0; i < static_cast<int>(listSize) && i < FD_SETSIZE; ++i) {
       Socket *pSock = static_cast<Socket*>(sockList.Element(i));
       if (pSock->sock > maxSocket)
          maxSocket = pSock->sock;  // max socket number
	   FD_SET(pSock->sock, pReadFd);
    }
	
	timeOutStruct.tv_sec = timeout / 1000;
	timeOutStruct.tv_usec = (timeout % 1000) * 1000;

    // wait for pending data
    return ::select((int)(maxSocket + 1), pReadFd, NULL, NULL, &timeOutStruct);
}

/**
 *   Check whether incoming data is available for a list of sockets
 *
 *   @param [in, out] sockList -- list of socket
 *                                on return, available sockets are remained in the list
 *                                and are moved to the front of the list
 *   @param [in] timeout -- timeout in milliseconds
 *
 *   @return > 0  -- number of sockets available for reading
 *   @return 0 -- time is up
 *   @return < 0  -- error
 */
int Socket::ChkPendingSockets(collections::ListCppObjT<Socket> &sockList, int timeout)
{
    fd_set readFd;
    int nRet = GetPendingSockets(sockList, sockList.GetCount(), &readFd, timeout);

    // check result
    if (nRet > 0) {
       for (int i = 0; i < static_cast<int>(sockList.GetCount()); ) {
          if ( ! FD_ISSET(sockList.Get(i)->sock, &readFd)) {
             sockList.RemoveAt(i);     
          }
          else {
             ++i; // advance to next if set
          }
       }
    }
    else {
       sockList.Clear();
    }

    return nRet;
}

/**
 *
 *   Check whether incoming data is available for a list of sockets
 *
 *   @param [in, out] sockArray -- array of socket
 *                                 on return, available sockets are remained in the array
 *                                 (non-available slots are set to NULL)
 *   @param [in] timeout -- timeout in milliseconds
 *
 *   @return > 0  -- number of sockets available for reading
 *   @return 0 -- time is up
 *   @return < 0  -- error
 */
int Socket::ChkPendingSockets(collections::ArrayCppObjT<Socket> &sockArray, int timeout)
{
    fd_set readFd;
    int nRet = GetPendingSockets(sockArray, sockArray.GetSize(), &readFd, timeout);

    // check result
    if (nRet > 0) {
       for (int i = 0; i < static_cast<int>(sockArray.GetSize()); ++i) {
          if ( ! FD_ISSET(sockArray.Get(i)->sock, &readFd)) 
             sockArray.Remove(i);
       }
    }
    else {
       sockArray.Clear();
    }

    return nRet;
}

} // end of namespace net

} // end of namespace cppflib

